﻿/* 
 * Produzido por:
 *      Daniel Vargas
 *      Danilo Valvezon
 *      Fernanda Osaki
 *      Tiago Ricardo Almeida da Silva
 *      
 * FATEC São Caetano do Sul
 * Jogos NA6
 * */

using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace bomberboyonline
{
    class TelaJogo : Microsoft.Xna.Framework.DrawableGameComponent
    {
        // Declaração de Variaveis gerais do jogo
        
        private Game game; // referencia do objeto do tipo game que contem todos os componentes do jogo
        
        private Texture2D texturaFundo; // textura do fundo do labirinto        
        
        private List<int> posicoesOcupadas; // lista de posicoes ocupadas por objetos colidiveis do jogo
        
        private List<int> posicoesBlocos; // lista de posicoes dos blocos (destrutiveis) do jogo
        private bool blocosAdd; // flag para o metodo update não carregar os blocos a todo momento

        private List<int> posicoesParedes; // lista de posicoes dos paredes (não destrutiveis) do jogo
        private bool paredesAdd; // flag para o metodo update não carregar as paredes a todo momento

        private List<int> posicoesItens; // lista de posicoes dos itens de evolução dos personagens contidos no jogo
        private bool itensAdd; // flag para o metodo update não carregar os itens espalhados pela tela a todo momento
       
        private bool trofeisAdd; // flag para o metodo update não carregar os itens espalhados pela tela a todo momento

        private List<int> posicoesFogo; // lista de posicoes dos fogos presentes no jogo
        
        private Personagem[] players; // arrays de players
        private int[] posicoesPlayers; // posicao dos players nos quadrantes
        private bool playersAdd; // flag para o metodo update não carregar os players a todo momento
        private int qtdPlayers; // Numero de players no jogo

        private Random random; // objeto para fazer a randomização na montagem do labirinto e na inserção de itens no jogo

        private SoundEffectInstance somfundo; // som de fundo

        private bool fimPartida; // Flag de fim partida
        private int contFimPartida; // variavel de controle para dar um tempo para troca de tela

        private bool fimJogo; // Flag de fim partida

        private bool telaPlacar; //Flag pra saber se a Tela Placar está ativa

        // Construtor da Classe
        public TelaJogo(Game game, int qtdPlayers)
            :base(game)
        {
            this.qtdPlayers = qtdPlayers;
            players = new Personagem[qtdPlayers]; // Inicialização do array de personagens
            this.game = game; // atribuição de valor a referencia
            texturaFundo = game.Content.Load<Texture2D>("arquivos\\floor"); // Carregamento do plano de fundo(labirinto)
            somfundo = Game.Content.Load<SoundEffect>("som\\BGM1").CreateInstance(); // Carregamento do som de fundo
            random = new Random(); // Instanciação do objeto random     
            posicoesPlayers = new int[4];
            fimPartida = false;
            contFimPartida = 100;
            telaPlacar = false;
        }

        /// <summary>
        /// Allows the game component to perform any initialization it needs to before starting
        /// to run.  This is where it can query for any required services and load content.
        /// </summary>
        public override void Initialize()
        {
            // inicializa todas as flags de controle de criação dos objetos como false.
            playersAdd = false;
            paredesAdd = false;
            blocosAdd = false;
            itensAdd = false; 
            montaLabirinto(); // popula todas as listas para inserção posterior dos objetos na tela do labirinto
            base.Initialize();
        }

        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime)
        {
            if (telaPlacar) // Se estiver na tela de Placar
            {
                if (!trofeisAdd)
                {
                    adicionaTrofeus();
                }
                KeyboardState ks = Keyboard.GetState(); // Pega os eventos de teclado
                if (ks.IsKeyDown(Keys.Escape))
                {
                    game.Exit(); // se a tecla pressionada é o ESC, finaliza o jogo
                }
                if (ks.IsKeyDown(Keys.Enter))
                {
                    reinicia();
                }
                
            }
            else // Se estiver na tela do jogo
            {
                // verifica se som de fundo terminou e inicia novamente. 
                if (somfundo.State == SoundState.Stopped)
                {
                    somfundo.Play();
                }
                // Verifica se os itens já foram inseridos no labirinto, senão insere-os
                if (!itensAdd)
                {
                    adicionaItens(); // cria os itens e os insere no jogo
                }
                // Verifica se os blocos já foram inseridos no labirinto, senão insere-os
                if (!blocosAdd)
                {
                    adicionaBlocos(); // cria os blocos e os insere no jogo
                }
                // Verifica se as paredes já foram inseridas no labirinto, senão insere-as
                if (!paredesAdd)
                {
                    adicionaParedes(); // cria as paredes e os insere no jogo
                }
                // Verifica se os players já foram inseridos no labirinto, senão insere-os
                if (!playersAdd)
                {
                    adicionaPlayers(); // cria os Players e os insere no jogo
                }

                KeyboardState ks = Keyboard.GetState(); // Pega os eventos de teclado
                if (ks.IsKeyDown(Keys.Escape))
                {
                    game.Exit(); // se a tecla pressionada é o ESC, finaliza o jogo
                }
                if (players[0].getMorreu() == false) // Se player 1 não morreu, trata eventos relacionado a este player
                {
                    if (ks.IsKeyDown(Keys.Up) || ks.IsKeyDown(Keys.Down) || ks.IsKeyDown(Keys.Left) || ks.IsKeyDown(Keys.Right))
                    {
                        players[0].movimentar(ks, ref posicoesOcupadas, ref posicoesItens); // passa as teclas de movimento pressionadas para o personagem tratar e realizar a ação correspondente a tecla.
                    }
                    if (ks.IsKeyDown(Keys.PageDown))
                    {
                        players[0].lancaBomba(ref posicoesOcupadas, ref posicoesItens, ref posicoesFogo); // player lanca bomba no labirinto, quando tecla é pressionada
                    }
                }

                if (ks.IsKeyDown(Keys.PageUp))
                {
                    players[0].detonaBombas(ref posicoesOcupadas); // player detona suas bombas
                }

                if (players[1].getMorreu() == false) // Se player 2 não morreu, trata eventos relacionado a este player
                {
                    if (ks.IsKeyDown(Keys.W) || ks.IsKeyDown(Keys.A) || ks.IsKeyDown(Keys.S) || ks.IsKeyDown(Keys.D))
                    {
                        players[1].movimentar(ks, ref posicoesOcupadas, ref posicoesItens); // Movimenta player
                    }
                    if (ks.IsKeyDown(Keys.Q))
                    {
                        players[1].lancaBomba(ref posicoesOcupadas, ref posicoesItens, ref posicoesFogo); // Lanca bombas 
                    }
                }
                if (ks.IsKeyDown(Keys.D1))
                {
                    players[1].detonaBombas(ref posicoesOcupadas); // Detona bombas
                }
                if (qtdPlayers > 2)
                {
                    if (players[2].getMorreu() == false) // Se player 3 não morreu, trata eventos relacionado a este player
                    {
                        if (ks.IsKeyDown(Keys.I) || ks.IsKeyDown(Keys.K) || ks.IsKeyDown(Keys.J) || ks.IsKeyDown(Keys.L))
                        {
                            players[2].movimentar(ks, ref posicoesOcupadas, ref posicoesItens); // Movimenta player
                        }
                        if (ks.IsKeyDown(Keys.D9))
                        {
                            players[2].lancaBomba(ref posicoesOcupadas, ref posicoesItens, ref posicoesFogo); // Lanca bombas 
                        }
                    }
                    if (ks.IsKeyDown(Keys.D0))
                    {
                        players[2].detonaBombas(ref posicoesOcupadas); // Detona bombas
                    }
                    if (qtdPlayers > 3)
                    {
                        if (players[3].getMorreu() == false) // Se player 3 não morreu, trata eventos relacionado a este player
                        {
                            if (ks.IsKeyDown(Keys.G) || ks.IsKeyDown(Keys.B) || ks.IsKeyDown(Keys.N) || ks.IsKeyDown(Keys.V))
                            {
                                players[3].movimentar(ks, ref posicoesOcupadas, ref posicoesItens); // Movimenta player
                            }
                            if (ks.IsKeyDown(Keys.C))
                            {
                                players[3].lancaBomba(ref posicoesOcupadas, ref posicoesItens, ref posicoesFogo); // Lanca bombas 
                            }
                        }
                        if (ks.IsKeyDown(Keys.X))
                        {
                            players[3].detonaBombas(ref posicoesOcupadas); // Detona bombas
                        }
                    }
                }
                verificaFim(); // Verifica se apenas 2 ou mais playes continuam vivos para continuar jogo
            }
            
            base.Update(gameTime);
        }

        private void reinicia()
        {
            if (fimJogo)
            {
                game.Components.Clear();
            }
            else
            {
                game.Components.Clear();
                game.Components.Add(this);
                for (int i = 0; i < qtdPlayers; i++)
                {
                    game.Components.Add(players[i]);
                }
            }
            trofeisAdd = false;
            telaPlacar = false;
            fimPartida = false;
            contFimPartida = 100;
            texturaFundo = game.Content.Load<Texture2D>("arquivos\\floor");
            montaLabirinto();
        }

        private void adicionaTrofeus()
        {            
           for (int i = 0; i < qtdPlayers; i++)
           {
               int posicaoP = 105 + (i * 2 * 17);
               players[i].inicializa(posicaoP);
               for (int y = 0; y < players[i].getVitoria(); y ++ )
               {
                   game.Components.Add(new Trofeu(game, posicaoP +2 + y*2));
               }                   
           }
           trofeisAdd = true;
           
        }

        // Metodo que verifica fim de partida
        private void verificaFim()
        {
            if (!telaPlacar)
            {
                if (fimPartida)
                {
                    if (contFimPartida == 0)
                    {
                        IEnumerable<Personagem> personagens = game.Components.OfType<Personagem>();
                        foreach (Personagem personagem in personagens)
                        {
                            if (personagem.Visible == true)
                            {
                                personagem.setVitoria();
                                personagem.Visible = false;
                                if (personagem.getVitoria() >= 3)
                                {
                                    fimJogo = true;
                                }
                            }
                        }
                        // Limpa todas variaveis e comeca tudo de novo
                        game.Components.Clear();
                        game.Components.Add(this);
                        for (int i = 0; i < qtdPlayers; i++)
                        {
                            game.Components.Add(players[i]);
                        }
                        playersAdd = false;
                        posicoesFogo.Clear();
                        posicoesOcupadas.Clear();
                        posicoesParedes.Clear();
                        paredesAdd = false;
                        posicoesBlocos.Clear();
                        blocosAdd = false;
                        posicoesItens.Clear();
                        itensAdd = false;
                        // aqui deve ser chamada a Tela de placar
                        montaTelaPlacar();
                    }
                    else
                    {
                        contFimPartida--;
                    }
                }
                else
                {
                    int qtd = 0;
                    IEnumerable<Personagem> ps = game.Components.OfType<Personagem>();
                    foreach (Personagem p in ps)
                    {
                        if (p.Visible == true)
                        {
                            qtd++;
                        }
                    }
                    if (qtd <= 1)
                    {
                        fimPartida = true;
                    }
                }
            }
        }

        private void montaTelaPlacar()
        {
            telaPlacar = true;
            somfundo.Stop();            
            texturaFundo = game.Content.Load<Texture2D>("arquivos\\placar");
            
        }

        // Metodo que adiciona blocos baseado na lista de posicoes que foi gerada no metodo montaLabirinto
        public void adicionaBlocos()
        {
            // codigo para adicionar blocos vas
            foreach(int i in posicoesBlocos)
            {
                Game.Components.Add(new Bloco(game,i)); // Cria bloco e adicona a game
            }
            blocosAdd = true; // atribui true para flag de criacao de blocos
        }

        // Metodo que adiciona players ao jogo
        public void adicionaPlayers()
        {
            if (game.Components.OfType<Personagem>().Count() > 0)
            {                
                for (int i = 0; i < qtdPlayers; i++)  
                {
                    game.Components.Remove(players[i]);
                    players[i].inicializa(posicoesPlayers[i]);
                    players[i].Visible = true;
                    game.Components.Add(players[i]);
                }
            }
            else 
            {
                for (int i = 0; i < qtdPlayers; i++)
                {

                    players[i] = new Personagem(game, i + 1, posicoesPlayers[i]);
                    Game.Components.Add(players[i]);
                }
            }            
            playersAdd = true; // atribui true para flag de criacao de players
        }

        public void adicionaParedes()
        {
            //codigo para adicionar paredes
            foreach (int i in posicoesParedes)
            {
                Game.Components.Add(new Parede(game, i)); // Cria parede e adiciona a jogo
            }
            paredesAdd = true; // atribui true para flag de criacao de paredes
        }

        public void adicionaItens()
        {
            Random r = new Random();
            //codigo para adicionar itens, por meio de uma funcao randomica calcula qual item ira adicionar 
            foreach (int pos in posicoesItens)
            {
                int aleatorio = r.Next(7); // Existem apenas 5 itens, mas a funcao dera um numero aleatorio até 7 para ter maiores chances de gerar itens mais comuns (0 = bomba extra,1 = poder fogo, 4= detonador)
                if (aleatorio == 5) aleatorio = 0; // caso numero seja igual a 5 atribui 0 a ele para gerar item 0
                if (aleatorio == 6) aleatorio = 1; // caso numero seja igual a 6 atribui 1 a ele para gerar item 1
                if (aleatorio == 7) aleatorio = 4; // caso numero seja igual a 7 atribui 4 a ele para gerar item 4
                Game.Components.Add(new Item(game,aleatorio, pos)); //cria item e adiciona ao jogo
            }
            itensAdd = true; // atribui true para flag de criacao de itens
            
        }

        /// <summary>
        /// Allows the game component to draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Draw(GameTime gameTime)
        {
            // Pega o Spritebatch do Services
            SpriteBatch sBatch = (SpriteBatch)Game.Services.GetService(typeof(SpriteBatch));
            // Desenha o fundo
            sBatch.Draw(texturaFundo, new Rectangle(0, 0, Game.Window.ClientBounds.Width, Game.Window.ClientBounds.Height), Color.White);            
            base.Draw(gameTime);
        }

        private void montaLabirinto()
        {
            // Inicializa listas de posicoes de objetos
            posicoesItens = new List<int>();
            posicoesBlocos = new List<int>();
            posicoesParedes = new List<int>();
            posicoesOcupadas = new List<int>();
            posicoesFogo = new List<int>();

            // Le arquivo que contem mapa
            StreamReader sr = new StreamReader("mapa.txt");            

            string sLine=""; // declaro variavel que vaoi conter cada linha do arquivo para manipular os caracteres
            int linha = 0; // declaro variavel para controlar linhas de posicoes dos objetos
            while(sLine!=null){ // enquanto linha é diferente de null
                sLine = sr.ReadLine(); // pego proxima linha
                if (sLine != null) // se linha é diferente de null
                {                    
                    char[] chars = sLine.ToCharArray(); // pego todos os caracteres e jogo num array
                    for(int i=0; i < chars.Count() ; i++) // para cada caractere
                    {
                        if (chars[i] == '#') // se for # adiciono na posicaoBLocos a posicao do caractere
                        {
                            int posicao = linha*17+i;
                            posicoesBlocos.Add(posicao);
                            colocaItem(posicao); // invoca o metodo randomico de insercao de item
                        }
                        else if (chars[i] == '%') // Se for % adiciono na lista de Paredes a posicao
                        {
                            posicoesParedes.Add(linha * 17 + i);
                        }
                        else if (chars[i] == '+') // Se for +
                        {
                            int rand = random.Next(4); // pego um numero randomico até 4
                            switch(rand)
                            {
                                case 1: // se for 1 adiciona a posicao na lista de blocos
                                    int posicao = linha * 17 + i;
                                    posicoesBlocos.Add(posicao);
                                    colocaItem(posicao); // verifico se havera item nesa posicao
                                    break;
                                case 2: // se for 2 adiciono posicao na lista de paredes
                                    posicoesParedes.Add(linha * 17 + i);
                                    break;
                                case 3: // se for 3 é um espaco vazio
                                    break;
                                default:
                                    break;
                            }
                        }
                        else if (chars[i] == '1') // se for 1 posicao do player 1
                        {                            
                            posicoesPlayers[0] = linha * 17 + i;
                        }
                        else if (chars[i] == '2') // se for 2 posicao do player 2
                        {
                            posicoesPlayers[1] = linha * 17 + i;
                        }
                        else if (chars[i] == '3') // se for 3 posicao do player 3
                        {
                            posicoesPlayers[2] = linha * 17 + i;
                        }
                        else if (chars[i] == '4') // se for 4 posicao do player 4
                        {
                            posicoesPlayers[3] = linha * 17 + i;
                        }

                    }
                    linha++; // incrementa linha para calcular posicao
                }
            }
            sr.Close(); // fecha arquivo
            posicoesOcupadas.AddRange(posicoesBlocos); // adiciona posicoes de blocos e paredes nas posicoes Ocupadas
            posicoesOcupadas.AddRange(posicoesParedes); // adiciona posicoes de blocos e paredes nas posicoes Ocupadas
        }

        // Metodo que faz uma funcao aleatorio pra ver se coloca ou nao item na posicao passada
        public void colocaItem(int posicao)
        {
            
            int aleatorio = random.Next(4); // faz-se um random até 4 pra ter poucas chances de ter item

            if (aleatorio == 1)
            {
                posicoesItens.Add(posicao); // se o aleatorio for 1 coloca item
            }
        }

    }
}
